Skip to content

Conversation

js07
Copy link
Collaborator

@js07 js07 commented Oct 13, 2025

WHY

Summary by CodeRabbit

Release Notes

  • New Features
    • Added comprehensive Slack v2 integration with 30+ actions including channel management, message operations, file handling, user management, and workflow automation.
    • Added 8 instant event sources for real-time Slack triggers including new messages, reactions, mentions, channel creation, and user additions.
    • Supports advanced messaging with Block Kit builder, threading, scheduling, and metadata.
    • Includes batch operations for listing channels, users, files, and group members with pagination support.

js07 added 12 commits October 13, 2025 16:13
- List users by real name in dropdowns
- List users by real name in action summary
- List files using bot token if available
- Get files using bot token if available
- Show configuration error if bot is not a member of the channel
- Add `addToChannel` prop to Send Message actions
- Check user's access to channel in message actions
- Use RTS API to search messages in Find Message action
- List only channels, not DMs, in 'file' actions
- List only channels, not DMs, in 'message' triggers
- Handle permission error fetching message in New Reaction trigger

Context:

In Slack's v2 OAuth flow, user access tokens and bot access tokens are
issued with separate scopes. User tokens are used to perform actions on
behalf of a user, while bot tokens are used for actions performed by the
app itself. To send a message to a private channel or DM, the bot must
be a member of that channel.

Due to permission restrictions, some endpoints require bot tokens for
access, such as viewing messages in DMs and listing files. As a result,
access to messages and files in DMs is no longer supported.
This allows us to support both existing and new Slack connections
created with Slack's v2 OAuth flow, with or without the deprecated
perspectival chat:write:user and chat:write:bot scopes:

| Slack auth | Token Types | Scopes                | as_user Values  |
|------------|-------------|-----------------------|-----------------|
| v1         | user        | chat:write:bot, :user | true, false     |
| v2         | user, bot   | chat:write:bot, :user | true, false     |
| v2         | user, bot   | chat:write            | true            |

In v2, when the chat:write scope is requested, it replaces
chat:write:bot and chat:write:user. Without the chat:write:bot scope,
as_user cannot be false; user tokens always post as the user, and bot
tokens always post as the bot. In v2, with or without the chat:write:bot
scope, we can use the bot token if as_user is false since it will have
permission to post as itself. And we MAY pass through as_user if it's
true since Slack allows it even when it's superfluous. However, we MUST
pass through as_user if it's true AND the user token still has the
chat:write:bot scope since otherwise the message will post as the bot
user instead of the user.

See:
https://docs.slack.dev/reference/methods/chat.postMessage/#legacy_as_user
so it is shown in the component form by default so users aren't suprised
by the new invite behavior
it's no longer supported for new auths since they will not have the
im:history scope
Fallback to search.messages API method if missing scopese
Future-proofs Slack components to support proxying requests
add/update logged messages when unable to add app to channel
The following components have new minor versions:

- delete-message
- find-message
- get-file
- list-files
- reply-to-a-message
- send-block-kit-message
- send-large-message
- send-message
- send-message-advanced
- send-message-to-channel
- send-message-to-user-or-group
- update-message
- new-keyword-mention
- new-message-in-channels
- new-user-mention

All other Slack components have new patch versions.
Copy link

vercel bot commented Oct 13, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Oct 20, 2025 0:22am
pipedream-docs-redirect-do-not-edit Ignored Ignored Oct 20, 2025 0:22am

💡 Enable Vercel Agent with $100 free credit for automated AI reviews

Copy link
Contributor

coderabbitai bot commented Oct 13, 2025

Walkthrough

Introduces a comprehensive Slack v2 component for Pipedream with 30+ actions (messaging, channel management, user operations, file handling), 8+ event sources (new messages, reactions, mentions), a Slack app integration module with extensive API methods, and common utilities including caching and constants.

Changes

Cohort / File(s) Summary
Slack v2 Actions - Message Operations
components/slack_v2/actions/send-message/..., components/slack_v2/actions/send-message-to-channel/..., components/slack_v2/actions/send-message-to-user-or-group/..., components/slack_v2/actions/send-message-advanced/..., components/slack_v2/actions/send-large-message/..., components/slack_v2/actions/reply-to-a-message/..., components/slack_v2/actions/send-block-kit-message/...
Send messages to channels, users, or groups; support threading, Block Kit formatting, metadata, scheduling, and Pipedream attribution flags.
Slack v2 Actions - Channel Management
components/slack_v2/actions/create-channel/..., components/slack_v2/actions/archive-channel/..., components/slack_v2/actions/set-channel-description/..., components/slack_v2/actions/set-channel-topic/...
Create, archive, and configure Slack channels with name normalization and metadata support.
Slack v2 Actions - Message Management
components/slack_v2/actions/update-message/..., components/slack_v2/actions/delete-message/..., components/slack_v2/actions/find-message/...
Update, delete, and search messages with support for threading and cursor-based pagination.
Slack v2 Actions - Member Management
components/slack_v2/actions/invite-user-to-channel/..., components/slack_v2/actions/kick-user/..., components/slack_v2/actions/update-group-members/..., components/slack_v2/actions/list-members-in-channel/..., components/slack_v2/actions/list-group-members/...
Invite, remove, and manage users in channels and groups with optional username resolution.
Slack v2 Actions - List Operations
components/slack_v2/actions/list-channels/..., components/slack_v2/actions/list-users/..., components/slack_v2/actions/list-files/..., components/slack_v2/actions/list-replies/...
Retrieve paginated lists of channels, users, files, and message threads.
Slack v2 Actions - File Operations
components/slack_v2/actions/get-file/..., components/slack_v2/actions/delete-file/..., components/slack_v2/actions/upload-file/...
Manage files with retrieval, deletion, and multi-phase upload with form-data handling.
Slack v2 Actions - User & Profile Management
components/slack_v2/actions/get-current-user/..., components/slack_v2/actions/find-user-by-email/..., components/slack_v2/actions/update-profile/..., components/slack_v2/actions/set-status/...
Retrieve user info, search by email, update profiles, and manage status.
Slack v2 Actions - Reactions & Reminders
components/slack_v2/actions/add-emoji-reaction/..., components/slack_v2/actions/create-reminder/..., components/slack_v2/actions/approve-workflow/...
Add reactions, create reminders, and build interactive approval workflows with buttons.
Slack v2 Common Utilities
components/slack_v2/actions/common/build-blocks.mjs, components/slack_v2/actions/common/send-message.mjs
Shared Block Kit builder with multi-step form UI and comprehensive send-message helper with unfurl, threading, and metadata support.
Slack v2 Core App & Configuration
components/slack_v2/slack_v2.app.mjs, components/slack_v2/package.json, components/slack_v2/common/constants.mjs
Main Slack v2 app with WebClient integration, retry logic, propDefinitions, and API methods; package metadata; shared constants (MAX_RESOURCES, LIMIT, CHANNEL_TYPE).
Slack v2 Event Sources - Messages
components/slack_v2/sources/new-message-in-channels/..., components/slack_v2/sources/new-keyword-mention/..., components/slack_v2/sources/new-user-mention/..., components/slack_v2/sources/list-replies/...
Emit events on messages with filtering by keyword, bot, threads, and optional name resolution.
Slack v2 Event Sources - Reactions & Interactions
components/slack_v2/sources/new-reaction-added/..., components/slack_v2/sources/new-interaction-event-received/...
Emit events on emoji reactions and block actions with emoji filtering and channel/action_id-based routing.
Slack v2 Event Sources - Workspace Events
components/slack_v2/sources/new-channel-created/..., components/slack_v2/sources/new-user-added/..., components/slack_v2/sources/new-saved-message/...
Emit events when channels are created, users join, or messages are saved.
Slack v2 Event Sources - Common Base
components/slack_v2/sources/common/base.mjs, components/slack_v2/sources/common/constants.mjs
Caching layer with time-based invalidation and size limits for name/info lookups; event subtype definitions and configuration.
Event Payloads & Documentation
components/slack_v2/sources/*/test-event.mjs, components/slack_v2/sources/new-interaction-event-received/README.md
Static test fixtures for each event source and configuration guide.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Action as Slack Action<br/>(send-message)
    participant Common as Common Helper<br/>(send-message)
    participant App as Slack v2 App
    participant SlackAPI as Slack API

    User->>Action: Provide channel, text, blocks
    Action->>Common: Build message payload
    Common->>Common: Generate blocks if BlockKit mode
    Common->>App: Get channel info
    App->>SlackAPI: conversationsInfo()
    SlackAPI-->>App: channel metadata
    Common->>Common: Append "Sent via Pipedream" block
    Common->>App: Post/Schedule message
    
    alt Schedule (post_at provided)
        App->>SlackAPI: chat.scheduleMessage
    else Immediate
        App->>SlackAPI: chat.postMessage
    end
    
    SlackAPI-->>App: response { ts, channel }
    App-->>Common: message metadata
    Common-->>Action: Success summary
    Action-->>User: Message sent ✓
Loading
sequenceDiagram
    participant Slack as Slack Workspace
    participant Webhook as Pipedream<br/>Apphook
    participant Source as Event Source<br/>(new-message-in-channels)
    participant Cache as Base (Caching)
    participant Emit as Emit Event

    Slack->>Webhook: Message posted event
    Webhook->>Source: processEvent()
    
    Source->>Source: Filter: type == 'message'?
    Source->>Source: Filter: allowed subtype?
    Source->>Source: Filter: ignoreBot?
    Source->>Source: Filter: ignoreThreads?
    
    alt resolveNames enabled
        Source->>Cache: getUserName(user_id)
        Cache->>Cache: Check cache (ttl: 1hr, size: 1000)
        
        alt Cached
            Cache-->>Source: cached name
        else Not cached
            Cache->>SlackAPI: users.info()
            SlackAPI-->>Cache: user profile
            Cache->>Cache: Store in cache
            Cache-->>Source: name
        end
        
        Source->>Source: Enrich event with names
    end
    
    Source->>Emit: Event ID + summary + ts
    Emit-->>User: Trigger workflow ✓
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Rationale: Despite 50+ new files, most actions follow a consistent, repetitive template (metadata, props, single API call, summary export) requiring quick pattern-based review. The slack_v2.app.mjs file is large with many methods (~40) but uses a straightforward delegation pattern to WebClient. The base.mjs caching layer, send-message utilities, and event source logic introduce moderate complexity with conditional rendering and state management. File spread is wide but interdependencies are minimal. Homogeneous pattern dominance reduces effective review scope.

Poem

🐰 Slack hops into Pipedream
Channels, threads, and messages stream,
Block Kit builds with care so keen,
Reactions, reminders, reactions gleam!
A warren of workflows now dreams so bright,
With webhooks and caches working just right! 🌙✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description consists solely of an empty "WHY" section placeholder ("") with no substantive content provided. The description lacks any explanation of the motivation, rationale, business context, or purpose for adding these Slack v2 components. While the repository has a minimal template requiring only a "WHY" section, this section is entirely unfilled, leaving no information about why this comprehensive component suite is being introduced. The author must complete the "WHY" section with substantive content explaining the motivation for these changes. This should include the business rationale, problem being solved, and any relevant context about why Slack v2 components are being added at this time. Without this information, reviewers lack essential context to evaluate the necessity and appropriateness of the changeset.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Slack v2 components" clearly and concisely describes the main addition to the codebase. The changeset introduces a comprehensive suite of Slack v2 components including action modules (archive channel, delete message, send message, etc.), source modules (new message detection, reaction tracking, etc.), common utilities, and the core app integration file. The title accurately reflects this primary change and is specific enough for teammates to understand the scope without being overly detailed.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch slack-v2-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@js07 js07 marked this pull request as ready for review October 15, 2025 20:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/slack/actions/send-large-message/send-large-message.mjs (1)

49-64: Fix metadata_event_payload handling to support object inputs.

When metadata_event_payload is provided as an object (not a string), metadataEventPayload remains undefined, resulting in event_payload: undefined in the metadata object. The code only assigns metadataEventPayload when parsing a string.

Apply this diff to handle both string and object inputs:

-    let metadataEventPayload;
+    let metadataEventPayload = this.metadata_event_payload;

     if (this.metadata_event_type) {
       if (typeof this.metadata_event_payload === "string") {
         try {
           metadataEventPayload = JSON.parse(this.metadata_event_payload);
         } catch (error) {
           throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`);
         }
       }

       this.metadata = {
         event_type: this.metadata_event_type,
         event_payload: metadataEventPayload,
       };
     }
components/slack/actions/common/send-message.mjs (1)

213-229: Fix metadata_event_payload handling to support object inputs.

When metadata_event_payload is provided as an object (not a string), metadataEventPayload remains undefined, resulting in event_payload: undefined in the metadata object. The code only assigns metadataEventPayload when parsing a string.

Apply this diff to handle both string and object inputs:

-    let metadataEventPayload;
+    let metadataEventPayload = this.metadata_event_payload;

     if (this.metadata_event_type) {

       if (typeof this.metadata_event_payload === "string") {
         try {
           metadataEventPayload = JSON.parse(this.metadata_event_payload);
         } catch (error) {
           throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`);
         }
       }

       this.metadata = {
         event_type: this.metadata_event_type,
         event_payload: metadataEventPayload,
       };
     }
🧹 Nitpick comments (1)
components/slack/actions/update-group-members/update-group-members.mjs (1)

54-55: Good defensive fix!

Adding default empty arrays prevents TypeError when the optional arrays are undefined, since line 62 calls .includes() and line 63 spreads the array.

Optionally, if a user appears in both the existing group members and usersToAdd, they may be duplicated in the final array (line 63). Consider verifying whether the Slack API deduplicates on their end, or add explicit deduplication if needed:

   users = users.filter((user) => !usersToRemove.includes(user));
-  users.push(...usersToAdd);
+  const allUsers = [...new Set([...users, ...usersToAdd])];
   const response = await this.slack.updateGroupMembers({
     usergroup: userGroup,
-    users,
+    users: allUsers,
     team_id: team,
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25fe600 and 18a1e8f.

📒 Files selected for processing (51)
  • components/azure_cosmos_db/azure_cosmos_db.app.mjs (1 hunks)
  • components/binalyze_air/binalyze_air.app.mjs (1 hunks)
  • components/brainbase_labs/brainbase_labs.app.mjs (1 hunks)
  • components/google_perspective/google_perspective.app.mjs (1 hunks)
  • components/n1n/n1n.app.mjs (1 hunks)
  • components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs (1 hunks)
  • components/slack/actions/approve-workflow/approve-workflow.mjs (1 hunks)
  • components/slack/actions/archive-channel/archive-channel.mjs (1 hunks)
  • components/slack/actions/common/send-message.mjs (4 hunks)
  • components/slack/actions/create-channel/create-channel.mjs (1 hunks)
  • components/slack/actions/create-reminder/create-reminder.mjs (1 hunks)
  • components/slack/actions/delete-file/delete-file.mjs (1 hunks)
  • components/slack/actions/delete-message/delete-message.mjs (1 hunks)
  • components/slack/actions/find-message/find-message.mjs (3 hunks)
  • components/slack/actions/find-user-by-email/find-user-by-email.mjs (1 hunks)
  • components/slack/actions/get-file/get-file.mjs (3 hunks)
  • components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs (1 hunks)
  • components/slack/actions/kick-user/kick-user.mjs (1 hunks)
  • components/slack/actions/list-channels/list-channels.mjs (1 hunks)
  • components/slack/actions/list-files/list-files.mjs (3 hunks)
  • components/slack/actions/list-group-members/list-group-members.mjs (1 hunks)
  • components/slack/actions/list-members-in-channel/list-members-in-channel.mjs (1 hunks)
  • components/slack/actions/list-replies/list-replies.mjs (2 hunks)
  • components/slack/actions/list-users/list-users.mjs (1 hunks)
  • components/slack/actions/reply-to-a-message/reply-to-a-message.mjs (1 hunks)
  • components/slack/actions/send-block-kit-message/send-block-kit-message.mjs (1 hunks)
  • components/slack/actions/send-large-message/send-large-message.mjs (3 hunks)
  • components/slack/actions/send-message-advanced/send-message-advanced.mjs (1 hunks)
  • components/slack/actions/send-message-to-channel/send-message-to-channel.mjs (1 hunks)
  • components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs (2 hunks)
  • components/slack/actions/send-message/send-message.mjs (1 hunks)
  • components/slack/actions/set-channel-description/set-channel-description.mjs (1 hunks)
  • components/slack/actions/set-channel-topic/set-channel-topic.mjs (1 hunks)
  • components/slack/actions/set-status/set-status.mjs (1 hunks)
  • components/slack/actions/update-group-members/update-group-members.mjs (2 hunks)
  • components/slack/actions/update-message/update-message.mjs (1 hunks)
  • components/slack/actions/update-profile/update-profile.mjs (1 hunks)
  • components/slack/actions/upload-file/upload-file.mjs (1 hunks)
  • components/slack/actions/verify-slack-signature/verify-slack-signature.mjs (0 hunks)
  • components/slack/package.json (1 hunks)
  • components/slack/slack.app.mjs (9 hunks)
  • components/slack/sources/new-channel-created/new-channel-created.mjs (1 hunks)
  • components/slack/sources/new-direct-message/new-direct-message.mjs (0 hunks)
  • components/slack/sources/new-direct-message/test-event.mjs (0 hunks)
  • components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs (1 hunks)
  • components/slack/sources/new-keyword-mention/new-keyword-mention.mjs (2 hunks)
  • components/slack/sources/new-message-in-channels/new-message-in-channels.mjs (2 hunks)
  • components/slack/sources/new-reaction-added/new-reaction-added.mjs (2 hunks)
  • components/slack/sources/new-saved-message/new-saved-message.mjs (1 hunks)
  • components/slack/sources/new-user-added/new-user-added.mjs (1 hunks)
  • components/slack/sources/new-user-mention/new-user-mention.mjs (2 hunks)
💤 Files with no reviewable changes (3)
  • components/slack/sources/new-direct-message/new-direct-message.mjs
  • components/slack/actions/verify-slack-signature/verify-slack-signature.mjs
  • components/slack/sources/new-direct-message/test-event.mjs
🧰 Additional context used
🧬 Code graph analysis (3)
components/slack/actions/common/send-message.mjs (2)
components/slack/slack.app.mjs (3)
  • channelId (24-27)
  • channel (505-507)
  • channel (514-516)
components/slack/actions/send-large-message/send-large-message.mjs (1)
  • channelName (91-91)
components/slack/actions/send-large-message/send-large-message.mjs (1)
components/slack/slack.app.mjs (2)
  • channel (505-507)
  • channel (514-516)
components/slack/slack.app.mjs (2)
components/slack/actions/get-file/get-file.mjs (1)
  • response (53-55)
components/slack/actions/find-message/find-message.mjs (3)
  • response (115-119)
  • response (134-138)
  • cursor (112-112)
🔇 Additional comments (29)
components/slack/sources/new-channel-created/new-channel-created.mjs (1)

8-8: Version bump looks good.

No issues spotted with the update to version 0.0.11.

components/slack/actions/archive-channel/archive-channel.mjs (1)

8-8: Version bump looks good.

Version increment to 0.0.25 is consistent and introduces no regressions.

components/slack/actions/set-channel-topic/set-channel-topic.mjs (1)

7-7: Version bump looks good.

Matches surrounding Slack action updates; no further changes needed.

components/slack/actions/update-profile/update-profile.mjs (1)

8-8: Version bump looks good.

Increment to 0.0.25 keeps this action aligned with the other Slack updates in the PR. No further action needed.

components/slack/actions/approve-workflow/approve-workflow.mjs (1)

8-8: Version bump looks good.

Matches the broader Slack release versioning pattern with no functional impact.

components/slack/sources/new-user-added/new-user-added.mjs (1)

8-8: Version bump looks good

The source metadata increment is consistent and introduces no risk.

components/slack/actions/create-reminder/create-reminder.mjs (1)

7-7: Version bump looks good

The metadata-only bump to 0.0.26 keeps this action aligned with the broader Slack release. 👍

components/slack/actions/send-block-kit-message/send-block-kit-message.mjs (1)

10-10: Version bump looks good.

Increment aligns with the broader Slack v2 rollout; no further action needed.

components/slack/actions/kick-user/kick-user.mjs (1)

8-8: Version bump looks consistent.

Matches the broader component release cadence. No further issues spotted.

components/azure_cosmos_db/azure_cosmos_db.app.mjs (1)

11-11: LGTM — object terminator restored.

Thanks for reintroducing the closing };; keeps the module syntax tidy.

components/slack/actions/reply-to-a-message/reply-to-a-message.mjs (1)

9-9: Version bump looks good

Increment to 0.2.0 is consistent with the ongoing Slack updates in this PR.

components/slack/sources/new-saved-message/new-saved-message.mjs (1)

8-8: Version bump looks good.

The patch version increment is appropriate for this maintenance update.

Note: Line 9 mentions this endpoint is deprecated by Slack and may be shut down in the future. Consider tracking this deprecation to ensure timely migration or removal when Slack discontinues support.

components/slack/actions/find-user-by-email/find-user-by-email.mjs (1)

7-7: LGTM! Version bump aligns with OAuth v2 compatibility.

The version increment appropriately marks this action as compatible with the underlying Slack OAuth v2 changes, even though no logic changes were required.

components/slack/actions/list-channels/list-channels.mjs (1)

7-7: Verify that no functional changes are needed for v2 OAuth support.

The version bump appears intentional for indicating v2 OAuth compatibility. Since this is a read-only action that only lists channels (as indicated by the readOnlyHint: true annotation), it likely doesn't require the additional props (like addToChannel) or pre-run logic that other Slack actions in this PR received.

However, please confirm that the underlying slack.app.mjs component handles all necessary v2 OAuth changes transparently, and that this action will function correctly without any code modifications.

components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)

7-7: LGTM: Version bump for coordinated release.

The metadata-only version bump aligns with the coordinated release pattern across Slack actions in this PR.

components/slack/actions/update-group-members/update-group-members.mjs (1)

7-7: LGTM!

Version bump is appropriate for this defensive code improvement.

components/slack/actions/list-replies/list-replies.mjs (3)

1-1: LGTM!

The constants import is correctly added and used for channel type constraints.


8-8: LGTM!

The version bump from 0.0.24 to 0.0.25 is appropriate for this change.


21-28: Confirm DM/group channel support isn't required: list-replies now limits the picker to CHANNEL_TYPE.PUBLIC and CHANNEL_TYPE.PRIVATE—verify no existing workflows depend on CHANNEL_TYPE.IM or CHANNEL_TYPE.MPIM.

components/slack/sources/new-reaction-added/new-reaction-added.mjs (2)

8-8: LGTM: Version bump aligns with error handling changes.

The minor version increment from 1.1.26 to 1.2.0 appropriately reflects the defensive error handling added to processEvent.


100-107: Approve with observability enhancements

  • Replace console.log with a structured logger or emit a metric on getMessage failure.
  • Downstream Slack handlers do not reference event.message, so undefined messages won’t break processing.
  • If message retrieval is critical, attach an error field to the returned event for better troubleshooting.
components/slack/actions/send-large-message/send-large-message.mjs (2)

38-42: LGTM! Pre-send channel invitation logic is correct.

The conditional check for addToChannel and subsequent call to maybeAddAppToChannels ensures the app is properly invited to the channel before sending the message. The addToChannel prop is correctly inherited from the common send-message module.


91-91: LGTM! Channel name resolution refactored to use helper.

Using getChannelDisplayName centralizes the channel display logic and improves maintainability.

components/slack/sources/new-user-mention/new-user-mention.mjs (1)

20-25: LGTM! Channel type constraints correctly applied.

The dynamic type constraints limit conversation selection to public and private channels using shared constants, aligning with the pattern adopted across other Slack sources and actions in this PR.

components/slack/actions/common/send-message.mjs (2)

12-17: LGTM! addToChannel prop and pre-send logic implemented correctly.

The new addToChannel prop allows users to opt into automatic channel invitation, and the pre-send logic efficiently caches channelId and invokes maybeAddAppToChannels when needed.

Also applies to: 190-196


258-258: LGTM! Channel name resolution refactored to use helper.

Using getChannelDisplayName centralizes the channel display logic and improves maintainability.

components/slack/sources/new-keyword-mention/new-keyword-mention.mjs (1)

20-25: LGTM! Channel type constraints correctly applied.

The dynamic type constraints limit conversation selection to public and private channels using shared constants, consistent with the pattern in new-user-mention.mjs and other Slack components.

components/slack/actions/get-file/get-file.mjs (1)

21-29: LGTM! All changes follow the established pattern.

The channel type constraints, addToChannel prop, and pre-run invitation logic are correctly implemented and consistent with the pattern adopted across other Slack actions in this PR.

Also applies to: 30-35, 47-51

components/slack/actions/list-files/list-files.mjs (1)

21-29: LGTM! All changes follow the established pattern.

The channel type constraints, addToChannel prop, and pre-run invitation logic are correctly implemented and consistent with the pattern adopted across other Slack actions in this PR.

Also applies to: 30-35, 64-68

Comment on lines 561 to 569
if ([
"not_in_channel",
"channel_not_found",
].some((errorType) => error.includes(errorType)) && asBot) {
const followUp = method.startsWith("chat.")
? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
: "Ensure the bot is a member of the channel.";
throw new ConfigurationError(`${error}\n${followUp}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix error handling before it throws a TypeError

error is typically an Error object (e.g., WebAPIPlatformError), so calling error.includes(...) throws TypeError: error.includes is not a function, hiding the original Slack error instead of surfacing it. Coerce the error to a string (or read error.data?.error) before running .includes, and use that value in the ConfigurationError message.

-        if ([
-          "not_in_channel",
-          "channel_not_found",
-        ].some((errorType) => error.includes(errorType)) && asBot) {
-          const followUp = method.startsWith("chat.")
-            ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
-            : "Ensure the bot is a member of the channel.";
-          throw new ConfigurationError(`${error}\n${followUp}`);
-        }
-        throw error;
+        const errorMessage = typeof error === "string"
+          ? error
+          : error?.data?.error || error.message || `${error}`;
+        if ([
+          "not_in_channel",
+          "channel_not_found",
+        ].some((errorType) => errorMessage.includes(errorType)) && asBot) {
+          const followUp = method.startsWith("chat.")
+            ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
+            : "Ensure the bot is a member of the channel.";
+          throw new ConfigurationError(`${errorMessage}\n${followUp}`);
+        }
+        throw error;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ([
"not_in_channel",
"channel_not_found",
].some((errorType) => error.includes(errorType)) && asBot) {
const followUp = method.startsWith("chat.")
? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
: "Ensure the bot is a member of the channel.";
throw new ConfigurationError(`${error}\n${followUp}`);
}
const errorMessage = typeof error === "string"
? error
: error?.data?.error || error.message || `${error}`;
if ([
"not_in_channel",
"channel_not_found",
].some((errorType) => errorMessage.includes(errorType)) && asBot) {
const followUp = method.startsWith("chat.")
? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
: "Ensure the bot is a member of the channel.";
throw new ConfigurationError(`${errorMessage}\n${followUp}`);
}
throw error;
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 561 to 569, the code assumes
`error` is a string and calls `error.includes(...)`, which throws a TypeError
when `error` is an Error object; change the logic to extract a safe string first
(e.g., const errMsg = typeof error === "string" ? error : error?.data?.error ??
String(error)), use `errMsg.includes(...)` for the conditional, and use `errMsg`
when constructing the ConfigurationError so the original Slack error is
preserved and no TypeError is thrown.

guard against unexpected types
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/slack/actions/find-message/find-message.mjs (1)

42-42: Fix stray backtick in description.

Clean up the string.

-      description: "Sort ascending (asc) or descending (desc)`",
+      description: "Sort ascending (asc) or descending (desc)",
♻️ Duplicate comments (1)
components/slack/slack.app.mjs (1)

561-569: Implement the error message extraction suggested in the previous review.

While ${error} prevents the TypeError, the previous review correctly noted that extracting error.data?.error would preserve the original Slack error message more accurately. The current string coercion might lose structured error details.

Apply the diff suggested in the previous review to properly extract and preserve error details.

🧹 Nitpick comments (7)
components/slack/slack.app.mjs (1)

502-527: Consider logging caught errors for debugging.

While the fallback strings provide good UX when name resolution fails, silently catching errors at lines 509 and 522 could hide legitimate issues (e.g., auth failures, rate limits). Consider adding console.error(err) or similar logging in the catch blocks to aid troubleshooting.

components/slack/actions/find-message/find-message.mjs (6)

80-90: Normalize merge precedence to avoid accidental overrides.

Currently, ...baseMessage and ...rest can override canonical fields (user, ts, text, type, channel). If the canonical fields should win, flip the spread order.

-      const normalized = {
-        type: "message",
-        user: authorUserId,
-        team: teamId,
-        ts: messageTs,
-        text: content,
-        permalink,
-        channel: channelInfo,
-        ...baseMessage,
-        ...rest,
-      };
+      const normalized = {
+        ...baseMessage,
+        ...rest,
+        type: "message",
+        user: authorUserId,
+        team: teamId,
+        ts: messageTs,
+        text: content,
+        permalink,
+        channel: channelInfo,
+      };

If overriding by baseMessage/rest is intentional, consider adding a brief comment to document the precedence.


115-119: Align with existing Slack client usage: pass throwRateLimitError.

Other Slack app calls set throwRateLimitError: true. Consider adding it here for consistency and clearer retry behavior. Based on relevant code snippets.

-        const response = await this.slack.assistantSearch({
+        const response = await this.slack.assistantSearch({
           ...baseParams,
           channel_types: "public_channel,private_channel",
           cursor,
+          throwRateLimitError: true,
         });

117-118: Channel types exclude DMs — intentional?

assistantSearch is limited to public/private channels. If you also want to match IM/MPIM messages, add im,mpim or make it configurable.

-          channel_types: "public_channel,private_channel",
+          channel_types: "public_channel,private_channel,im,mpim",

134-138: Also pass throwRateLimitError to searchMessages.

Match the pattern used across the Slack app for consistent error semantics. Based on relevant code snippets.

-        const response = await this.slack.searchMessages({
+        const response = await this.slack.searchMessages({
           ...baseParams,
           count,
           page,
+          throwRateLimitError: true,
         });

190-194: Avoid sending undefined params to the API.

Only include sort/sort_dir when set to reduce risk of invalid query params.

-    const baseParams = {
-      query: this.query,
-      sort: this.sort,
-      sort_dir: this.sortDirection,
-    };
+    const baseParams = {
+      query: this.query,
+      ...(this.sort && { sort: this.sort }),
+      ...(this.sortDirection && { sort_dir: this.sortDirection }),
+    };

197-205: Optional: Fallback if assistantSearch is unavailable.

If older app versions or tokens don’t expose assistantSearch, pre-check and use searchMessages directly.

-    try {
-      matches = await this.searchWithAssistant(baseParams, maxResults);
-    } catch (error) {
+    try {
+      if (typeof this.slack.assistantSearch !== "function") {
+        matches = await this.searchWithSearchMessages(baseParams, maxResults);
+      } else {
+        matches = await this.searchWithAssistant(baseParams, maxResults);
+      }
+    } catch (error) {
       if (this.shouldFallbackToSearchMessages(error)) {
         matches = await this.searchWithSearchMessages(baseParams, maxResults);
       } else {
         throw error;
       }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18a1e8f and 2df5de6.

📒 Files selected for processing (2)
  • components/slack/actions/find-message/find-message.mjs (3 hunks)
  • components/slack/slack.app.mjs (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/slack/actions/find-message/find-message.mjs (1)
components/slack/slack.app.mjs (8)
  • channel (505-507)
  • channel (514-516)
  • cursor (187-196)
  • cursor (240-247)
  • cursor (629-629)
  • cursor (653-653)
  • response (557-557)
  • page (293-298)
components/slack/slack.app.mjs (2)
components/slack/actions/find-message/find-message.mjs (3)
  • response (115-119)
  • response (134-138)
  • cursor (112-112)
components/slack/actions/get-file/get-file.mjs (1)
  • response (53-55)
🔇 Additional comments (9)
components/slack/slack.app.mjs (7)

5-5: LGTM: ConfigurationError import supports better error handling.

The import enables throwing user-friendly configuration errors when bot permissions are insufficient, improving the developer experience.


31-32: LGTM: Name resolution improvements are well-implemented.

The changes correctly:

  • Filter out falsy user IDs before lookup
  • Optimize by resolving IDs and usernames in parallel
  • Extract usernames from MPIM purpose values using regex

The inline comment at lines 140-142 clearly explains the optimization rationale.

Also applies to: 124-143


468-473: LGTM: addToChannel prop provides good user control.

The prop definition is clear, with a sensible default that automates the common case while allowing users to opt out when needed.


491-501: LGTM: Token selection logic is clear and correct.

The methods properly implement the bot token precedence: use bot token when explicitly requested via asBot and available, otherwise fall back to user token.


532-536: LGTM: SDK initialization properly supports token options.

The method correctly passes options to getToken for bot vs. user token selection and includes slackApiUrl configuration for custom Slack instances.


679-709: LGTM: Robust error handling for channel invitations.

The method correctly:

  • Checks for bot token availability
  • Validates bot_id before proceeding
  • Handles per-channel errors gracefully with logging
  • Skips DM/group DM channels automatically (per the comment)

Note: Sequential channel processing (lines 694-705) could be slow for many channels, but this is likely necessary given Slack's API constraints.


948-973: LGTM: File methods correctly use bot token with clear documentation.

The comments at lines 952-954 and 968-970 clearly explain that the bot token is required because the files:read scope is only requested for bot tokens in the Pipedream app. This prevents auth errors when using file operations.

components/slack/actions/find-message/find-message.mjs (2)

6-7: Verify the new docs endpoint and scope expectations.

Confirm that assistant.search.context is the intended API reference for this action and that required scopes match the implementation and fallback logic.


163-170: Good fix: guarded includes on errorCode.

The optional chaining prevents TypeError when errorCode is falsy. This resolves the earlier review concern.

Comment on lines 652 to 678
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
let cursor;
const realNames = {};
do {
const {
members: users,
response_metadata: { next_cursor: nextCursor },
} = await this.usersList({
limit: constants.LIMIT,
cursor,
throwRateLimitError,
...args,
});

for (const user of users) {
if (ids.includes(user.id)) {
realNames[user.id] = user.profile.real_name;
}
if (usernames.includes(user.name)) {
realNames[user.name] = user.profile.real_name;
}
}

cursor = nextCursor;
} while (cursor && Object.keys(realNames).length < (ids.length + usernames.length));
return realNames;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix potential infinite loop and performance issue.

Two issues in realNameLookup:

  1. Infinite loop risk (line 676): If some IDs or usernames don't exist in the workspace, realNames will never reach the expected count, causing the loop to paginate indefinitely.

  2. O(n²) performance (lines 667, 670): Using includes() on arrays inside a loop is inefficient for large lists.

Apply this diff to fix both issues:

-    async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
+    async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
+      const idSet = new Set(ids);
+      const usernameSet = new Set(usernames);
       let cursor;
       const realNames = {};
+      const targetCount = ids.length + usernames.length;
       do {
         const {
           members: users,
           response_metadata: { next_cursor: nextCursor },
         } = await this.usersList({
           limit: constants.LIMIT,
           cursor,
           throwRateLimitError,
           ...args,
         });
 
         for (const user of users) {
-          if (ids.includes(user.id)) {
+          if (idSet.has(user.id)) {
             realNames[user.id] = user.profile.real_name;
           }
-          if (usernames.includes(user.name)) {
+          if (usernameSet.has(user.name)) {
             realNames[user.name] = user.profile.real_name;
           }
         }
 
         cursor = nextCursor;
-      } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length));
+      } while (cursor && Object.keys(realNames).length < targetCount);
       return realNames;
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
let cursor;
const realNames = {};
do {
const {
members: users,
response_metadata: { next_cursor: nextCursor },
} = await this.usersList({
limit: constants.LIMIT,
cursor,
throwRateLimitError,
...args,
});
for (const user of users) {
if (ids.includes(user.id)) {
realNames[user.id] = user.profile.real_name;
}
if (usernames.includes(user.name)) {
realNames[user.name] = user.profile.real_name;
}
}
cursor = nextCursor;
} while (cursor && Object.keys(realNames).length < (ids.length + usernames.length));
return realNames;
},
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
const idSet = new Set(ids);
const usernameSet = new Set(usernames);
let cursor;
const realNames = {};
const targetCount = ids.length + usernames.length;
do {
const {
members: users,
response_metadata: { next_cursor: nextCursor },
} = await this.usersList({
limit: constants.LIMIT,
cursor,
throwRateLimitError,
...args,
});
for (const user of users) {
if (idSet.has(user.id)) {
realNames[user.id] = user.profile.real_name;
}
if (usernameSet.has(user.name)) {
realNames[user.name] = user.profile.real_name;
}
}
cursor = nextCursor;
} while (cursor && Object.keys(realNames).length < targetCount);
return realNames;
},
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 652 to 678, fix the potential
infinite loop and O(n²) performance by converting ids and usernames to Sets
before pagination, then on each page iterate members once and check membership
against those Sets (removing found entries from the Sets and mapping id/username
to real_name), and stop looping when either the Sets are empty (all requested
items resolved) or there is no next_cursor from usersList; also defensively
handle missing response_metadata/next_cursor to avoid relying on undefined and
thus prevent infinite pagination.

Comment on lines 895 to 900
assistantSearch(args = {}) {
args.count ||= constants.LIMIT;
return this.sdk().apiCall("assistant.search.context", {
...args,
});
},
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify whether assistantSearch should use makeRequest for consistency.

Unlike other methods, assistantSearch calls sdk().apiCall() directly, bypassing the retry logic and rate limit handling provided by makeRequest and _withRetries. This could lead to inconsistent behavior compared to other Slack API calls.

If the assistant.search.context API has different requirements that necessitate direct SDK usage, please document this with a comment. Otherwise, consider refactoring to use makeRequest for consistency.


🏁 Script executed:

#!/bin/bash
# Description: Check if other methods use direct apiCall instead of makeRequest

# Search for other instances of direct apiCall usage
rg -n "sdk\(\)\.apiCall" components/slack/

Length of output: 96


🏁 Script executed:

#!/bin/bash
rg -n "this\\.makeRequest" components/slack/slack.app.mjs -C3

Length of output: 7042


🏁 Script executed:

#!/bin/bash
# Search for makeRequest method definition
rg -n "makeRequest\s*\(" components/slack/slack.app.mjs -A10 | head -30

Length of output: 1074


🏁 Script executed:

#!/bin/bash
# Search for _withRetries within makeRequest method
rg -n "_withRetries" components/slack/slack.app.mjs -C2 | head -40

Length of output: 333


Refactor assistantSearch to use makeRequest for consistency with all other API methods.

The method uses direct sdk().apiCall() instead of makeRequest, bypassing the retry mechanism (3 retries) and rate-limit handling provided by _withRetries that every other API method in this file uses. This creates inconsistent error handling and resilience across the codebase.

At lines 895–900, change:

return this.sdk().apiCall("assistant.search.context", {
  ...args,
});

To:

return this.makeRequest({
  method: "assistant.search.context",
  ...args,
});
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 895 to 900, assistantSearch
currently calls this.sdk().apiCall directly (bypassing retries/rate-limit) and
should be changed to use this.makeRequest to get the same retry and rate-limit
behavior; set args.count default as before, then return this.makeRequest with
method: "assistant.search.context" and spread the rest of args so the call
signature and returned value remain identical but now uses the shared retry
wrapper.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @slack/web-api npm package doesn't currently expose a method on WebClient for this API method, so we can't call it like the other methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@js07, thank you for the clarification! That makes perfect sense—if the @slack/web-api package doesn't expose assistant.search.context as a method on WebClient, then using apiCall() directly is the correct approach.

To help future maintainers understand why this method differs from the others, consider adding a brief comment above the method:

// Uses apiCall directly since assistant.search.context is not exposed as a method on WebClient
assistantSearch(args = {}) {
  args.count ||= constants.LIMIT;
  return this.sdk().apiCall("assistant.search.context", {
    ...args,
  });
}

This will make it clear that the direct apiCall() usage is intentional due to SDK limitations.


✏️ Learnings added
Learnt from: js07
PR: PipedreamHQ/pipedream#18744
File: components/slack/slack.app.mjs:0-0
Timestamp: 2025-10-20T00:28:38.702Z
Learning: In components/slack/slack.app.mjs, the assistantSearch method uses sdk().apiCall() directly instead of makeRequest because the slack/web-api npm package doesn't expose assistant.search.context as a method on WebClient. This direct usage is intentional and necessary when Slack API methods are not yet exposed in the SDK.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@js07 js07 changed the title Support Slack v2 OAuth in components Slack v2 components Oct 20, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 34

🧹 Nitpick comments (36)
components/slack_v2/sources/new-interaction-event-received/README.md (1)

81-85: Vary sentence structure in troubleshooting steps to improve readability.

Three consecutive list items all begin with "Make sure that", which creates repetitive reading. Vary the phrasing for better flow.

 1. Make sure that your `action_id` or ****************channels**************** filters apply to that message, remove the filters to make sure that's not the case.
 
-1. Make sure that the message comes from the same Slack account that this source is configured with.
+1. Verify the message comes from the same Slack account that this source is configured with.
 
-1. Make sure that the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app.
+1. Confirm the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app.
components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (2)

24-41: Define label/description for slackApphook; remove eslint disable.

Improve clarity and satisfy lint without disabling rules.

-    // eslint-disable-next-line pipedream/props-description,pipedream/props-label
     slackApphook: {
       type: "$.interface.apphook",
       appProp: "slack",
+      label: "Slack Apphook",
+      description: "Internal apphook used to receive Slack events. Do not modify.",
       async eventNames() {

71-79: Simplify emoji parsing; iconEmoji is already a string[] prop.

Current JSON/CSV parsing is unnecessary and fragile. Prefer normalized array handling.

-      let iconEmojiParsed = [];
-
-      try {
-        iconEmojiParsed = typeof this.iconEmoji === "string" ?
-          JSON.parse(this.iconEmoji) :
-          this.iconEmoji;
-      } catch (error) {
-        iconEmojiParsed = this.iconEmoji.replace(/\s+/g, "").split(",");
-      }
+      const iconEmojiParsed = Array.isArray(this.iconEmoji)
+        ? this.iconEmoji.map((e) => e.trim()).filter(Boolean)
+        : [];
components/slack_v2/package.json (1)

1-22: Add license/engines and pin critical deps for reproducible builds.

  • Include a license field and Node engines (Pipedream runs Node 18+).
  • Consider pinning to exact versions for stability, especially @pipedream/platform (recommended to pin 3.1.0). Based on learnings.
 {
   "name": "@pipedream/slack_v2",
   "version": "0.0.1",
   "description": "Pipedream Slack_v2 Components",
   "main": "slack_v2.app.mjs",
+  "license": "MIT",
+  "engines": { "node": ">=18" },
   "keywords": [
@@
   "dependencies": {
-    "@pipedream/platform": "^3.1.0",
-    "@slack/web-api": "^7.9.0",
+    "@pipedream/platform": "3.1.0",
+    "@slack/web-api": "^7.9.0",
     "async-retry": "^1.3.3",
     "lodash": "^4.17.21"
   }
 }

Optionally add "repository" metadata.
To confirm latest safe @slack/web-api compatible with Node 18, please verify current releases and changelog.

components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs (1)

23-31: LGTM; consider clearer handling when user isn’t found.

Slack returns ok: false with users_not_found. Optionally set a friendly summary or throw when not found.

     const response = await this.slack.lookupUserByEmail({
       email: this.email,
     });
-    if (response.ok) {
+    if (response.ok) {
       $.export("$summary", `Successfully found user with ID ${response.user.id}`);
-    }
+    } else if (response.error === "users_not_found") {
+      $.export("$summary", "No user found for the provided email");
+    }
     return response;
components/slack_v2/common/constants.mjs (1)

11-24: Inconsistent value mapping in CHANNEL_TYPE_OPTIONS.

The first option uses "Channels" as its value instead of a CHANNEL_TYPE constant. This breaks the pattern established by the other two options (which use CHANNEL_TYPE.MPIM and CHANNEL_TYPE.IM). While this appears intentional (based on usage in approve-workflow.mjs where "Channels" maps to both PUBLIC and PRIVATE channel types), it creates an undocumented special case that could confuse maintainers.

Consider either:

  1. Adding a comment explaining that "Channels" is a special value representing both PUBLIC and PRIVATE channels, or
  2. Refactoring to use a more explicit constant like CHANNEL_TYPE.PUBLIC_AND_PRIVATE or an array value for consistency.
components/slack_v2/actions/set-status/set-status.mjs (1)

37-48: Consider omitting optional properties when not provided.

Lines 41 and 42-43 use the && operator to conditionally set status_emoji and status_expiration. When these optional props are not provided, the properties will be set to falsy values (e.g., undefined, false, 0) rather than being omitted from the object. Some APIs may not handle falsy values gracefully.

Consider using the spread operator to omit properties entirely when not provided:

 const response = await this.slack.updateProfile({
   profile: {
     status_text: this.statusText,
-    status_emoji: this.statusEmoji && `:${this.statusEmoji}:`,
-    status_expiration: this.statusExpiration
-      && Math.floor(new Date(this.statusExpiration).getTime() / 1000),
+    ...(this.statusEmoji && { status_emoji: `:${this.statusEmoji}:` }),
+    ...(this.statusExpiration && {
+      status_expiration: Math.floor(new Date(this.statusExpiration).getTime() / 1000),
+    }),
   },
 });
components/slack_v2/actions/list-group-members/list-group-members.mjs (1)

61-65: Optional: Remove unnecessary optional chaining.

Since members is initialized as an empty array on line 44, it can never be null or undefined, making the optional chaining on line 61 unnecessary.

-    if (members?.length) {
+    if (members.length) {
components/slack_v2/actions/update-message/update-message.mjs (1)

48-58: LGTM! Consider enhancing the summary with context.

The implementation correctly maps parameters and calls the Slack API. The generic success message is acceptable, though including the channel or timestamp could make debugging easier.

Optional enhancement:

-    $.export("$summary", "Successfully updated message");
+    $.export("$summary", `Successfully updated message in channel ${this.conversation}`);
components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs (1)

24-34: Make channel multi-select via propDefinition options, not parallel type fields

Declare multi-select in the propDefinition to avoid schema drift between type: "string[]" and the underlying definition.

Apply:

-    conversations: {
-      propDefinition: [
-        common.props.slack,
-        "conversation",
-      ],
-      type: "string[]",
-      label: "Channels",
-      description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.",
-      optional: true,
-      default: [],
-    },
+    conversations: {
+      propDefinition: [
+        common.props.slack,
+        "conversation",
+        () => ({ multiple: true }),
+      ],
+      label: "Channels",
+      description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.",
+      optional: true,
+    },
components/slack_v2/actions/upload-file/upload-file.mjs (2)

52-76: Use metadata-derived filename consistently

Use metadata.name for both the presign request and the form field to avoid mismatches with path parsing.

Apply:

-    const filename = this.content.split("/").pop();
+    const filename = metadata.name;
...
-    formData.append("filename", filename);
+    formData.append("filename", filename);

98-104: Consistent error type + more informative summary

Use the same error style as presign and include filename/channel in the summary.

Apply:

-    if (!completeUploadResponse.ok) {
-      throw new Error(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`);
-    }
+    if (!completeUploadResponse?.ok) {
+      throw new ConfigurationError(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`);
+    }
-
-    $.export("$summary", "Successfully uploaded file");
-    return completeUploadResponse;
+    $.export("$summary", `Successfully uploaded ${metadata.name} to ${this.conversation}`);
+    return completeUploadResponse;
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)

17-33: Consider removing redundant slack prop definition.

Line 18 redefines slack: common.props.slack, but line 31 already spreads ...common.props which includes the slack prop. While this doesn't cause an error (the spread overwrites the earlier definition), it creates unnecessary redundancy.

Apply this diff to remove the redundancy:

  props: {
-   slack: common.props.slack,
    conversation: {
      propDefinition: [
        common.props.slack,
        "conversation",
      ],
    },
    text: {
      type: "string",
      label: "Notification Text",
      description: "Optionally provide a string for Slack to display as the new message notification (if you do not provide this, notification will be blank).",
      optional: true,
    },
    ...common.props,
    ...buildBlocks.props,
  },
components/slack_v2/actions/list-files/list-files.mjs (1)

80-85: Consider using a boolean for hasMore.

Line 83 assigns hasMore = files.length, making it a number rather than a boolean. While this works in the loop condition, it reduces code clarity. Consider using hasMore = files.length > 0 or hasMore = files.length === this.pageSize for clearer intent.

Apply this diff to improve clarity:

    do {
      const { files } = await this.slack.listFiles(params);
      allFiles.push(...files);
-     hasMore = files.length;
+     hasMore = files.length > 0;
      params.page++;
    } while (hasMore && params.page <= this.numPages);
components/slack_v2/sources/new-saved-message/new-saved-message.mjs (1)

25-30: Improve summary for observability

Consider including user and channel in the summary for quick triage, e.g., “ saved a message in ”.

-    getSummary() {
-      return "New saved message";
-    },
+    getSummary(event) {
+      const user = event?.user || "Someone";
+      const channel = event?.item?.channel || "a channel";
+      return `${user} saved a message in ${channel}`;
+    },
components/slack_v2/actions/update-group-members/update-group-members.mjs (1)

69-70: Skip no-op updates, and align summary variable

Avoid the API call when membership is unchanged; also use the local userGroup for consistency.

-    $.export("$summary", `Successfully updated members of group with ID ${this.userGroup}`);
-    return response;
+    if (response?.ok === false) return response;
+    $.export("$summary", `Updated members of group ${userGroup} (${users.length} users)`);
+    return response;
components/slack_v2/actions/find-message/find-message.mjs (1)

51-66: Guard against cursor loops and overfetch

Add a seen‑cursor set to avoid infinite loops if an API bug returns the same cursor; stop when reaching maxResults.

   async searchWithAssistant(baseParams, maxResults) {
     const matches = [];
-    let cursor;
+    let cursor;
+    const seen = new Set();
 
     do {
       const response = await this.slack.assistantSearch({
         ...baseParams,
         channel_types: "public_channel,private_channel",
         cursor,
       });
       matches.push(...response.results?.messages || []);
-      cursor = response.response_metadata?.next_cursor;
-    } while (cursor && matches.length < maxResults);
+      const next = response.response_metadata?.next_cursor;
+      if (!next || seen.has(next) || matches.length >= maxResults) break;
+      seen.add(next);
+      cursor = next;
+    } while (true);
 
     return matches.slice(0, maxResults);
   },
components/slack_v2/actions/list-users/list-users.mjs (1)

44-49: Defensive default for missing members

Guard against undefined members to avoid pushing undefined.

-      users.push(...members);
+      users.push(...(members ?? []));
components/slack_v2/actions/send-large-message/send-large-message.mjs (3)

15-36: Props spread order causes duplication and overrides

You define slack, conversation, text, mrkdwn then spread ...common.props, which overrides them. Either remove duplicates or spread first and override intentionally.

-  props: {
-    slack: common.props.slack,
-    conversation: { propDefinition: [ common.props.slack, "conversation" ] },
-    text: { propDefinition: [ common.props.slack, "text" ] },
-    mrkdwn: { propDefinition: [ common.props.slack, "mrkdwn" ] },
-    ...common.props,
-  },
+  props: {
+    ...common.props,
+    // override here only if you need different labels/descriptions/defaults:
+    // conversation: { ... },
+    // text: { ... },
+    // mrkdwn: { ... },
+  },

44-47: Optional: safer “sent via Pipedream” text extraction

Guard against unexpected block structure when appending the flag text.

-      const sentViaPipedreamText = this._makeSentViaPipedreamBlock();
-      this.text += `\n\n\n${sentViaPipedreamText.elements[0].text}`;
+      const block = this._makeSentViaPipedreamBlock();
+      const text = block?.elements?.[0]?.text;
+      if (text) this.text += `\n\n\n${text}`;

66-87: Consider adding message chunking or file upload fallback for optimal Slack delivery

Messages sent via this action can exceed Slack's recommended text length for best results. Slack recommends keeping top-level text to approximately 4,000 characters for readability, and messages longer than 40,000 characters are truncated with a warning returned. Consider chunking this.text into multiple messages or implementing a fallback to file upload when the message size exceeds the ~4,000 character threshold.

components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs (1)

79-86: Tighten comparisons and parallelize name resolution.

Use strict equality and resolve names concurrently to reduce latency under load.

-      if ((this.ignoreBot) && (event.subtype == "bot_message" || event.bot_id)) {
+      if ((this.ignoreBot) && (event.subtype === "bot_message" || event.bot_id)) {
         return;
       }
@@
-      if (this.resolveNames) {
-        if (event.user) {
-          event.user_id = event.user;
-          event.user = await this.getUserName(event.user);
-        } else if (event.bot_id) {
-          event.bot = await this.getBotName(event.bot_id);
-        }
-        event.channel_id = event.channel;
-        event.channel = await this.getConversationName(event.channel);
-        if (event.team) {
-          event.team_id = event.team;
-          event.team = await this.getTeamName(event.team);
-        }
-      }
+      if (this.resolveNames) {
+        const promises = [];
+        if (event.user) {
+          event.user_id = event.user;
+          promises.push(this.getUserName(event.user).then((name) => { event.user = name; }));
+        } else if (event.bot_id) {
+          promises.push(this.getBotName(event.bot_id).then((name) => { event.bot = name; }));
+        }
+        event.channel_id = event.channel;
+        promises.push(this.getConversationName(event.channel).then((name) => { event.channel = name; }));
+        if (event.team) {
+          event.team_id = event.team;
+          promises.push(this.getTeamName(event.team).then((name) => { event.team = name; }));
+        }
+        await Promise.all(promises);
+      }

Also applies to: 88-101

components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)

18-19: Avoid duplicate slack prop; optionally expose raw blocks input.

  • slack is already provided via ...common.props; defining it again is redundant.
  • Expose the app’s blocks prop so users can paste raw Block Kit JSON when not using the builder.
   props: {
-    slack: common.props.slack,
+    // rely on ...common.props for `slack`
     conversation: {
       propDefinition: [
         common.props.slack,
         "conversation",
       ],
     },
@@
     link_names: {
       propDefinition: [
         common.props.slack,
         "link_names",
       ],
     },
+    blocks: {
+      propDefinition: [
+        common.props.slack,
+        "blocks",
+      ],
+    },
     ...common.props,
     ...buildBlocks.props,
   },

Also applies to: 56-58

components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1)

49-56: Harden pagination and results (handle missing members, dedupe IDs).

Prevents runtime errors if members is undefined and avoids duplicate IDs across pages; minor efficiency win before username lookup.

   do {
     const {
-      members, response_metadata: { next_cursor: nextCursor },
+      members, response_metadata: { next_cursor: nextCursor },
     } = await this.slack.listChannelMembers(params);
-    channelMembers.push(...members);
+    channelMembers.push(...(members ?? []));
     params.cursor = nextCursor;
     page++;
   } while (params.cursor && page < this.numPages);

+  // Deduplicate before optional username lookup
+  channelMembers = [...new Set(channelMembers)];

Also applies to: 58-64

components/slack_v2/slack_v2.app.mjs (5)

628-651: Speed up ID lookups with a Set (avoid O(n²)).

Current ids.includes(user.id) scales poorly on large teams.

-  let cursor;
-  const userNames = {};
+  let cursor;
+  const userNames = {};
+  const target = new Set(ids);
   do {
@@
-    for (const user of users) {
-      if (ids.includes(user.id)) {
-        userNames[user.id] = user.name;
-      }
-    }
+    for (const user of users) {
+      if (target.has(user.id)) userNames[user.id] = user.name;
+    }

652-678: Same Set optimization for realNameLookup.

-  let cursor;
-  const realNames = {};
+  let cursor;
+  const realNames = {};
+  const idSet = new Set(ids);
+  const unameSet = new Set(usernames);
@@
-    for (const user of users) {
-      if (ids.includes(user.id)) {
-        realNames[user.id] = user.profile.real_name;
-      }
-      if (usernames.includes(user.name)) {
-        realNames[user.name] = user.profile.real_name;
-      }
-    }
+    for (const user of users) {
+      if (idSet.has(user.id)) realNames[user.id] = user.profile.real_name;
+      if (unameSet.has(user.name)) realNames[user.name] = user.profile.real_name;
+    }

476-487: Potential label bug: resource.user.name likely undefined.

For DMs, resource.user is usually a user ID string, not an object. This returns Direct Messaging with: @undefined.

 getChannelLabel(resource) {
-  if (resource.user) {
-    return `Direct Messaging with: @${resource.user.name}`;
-  }
+  if (resource.user) return `Direct Messaging with: @${resource.user}`;
   const {
     is_private: isPrivate,
     name,
   } = resource.channel;
   return `${isPrivate && "Private" || "Public"} channel #${name}`;
 }

502-511: Simplify display name fallback for DMs.

Avoid redundant profile.real_name || profile?.real_name and add display_name fallback.

-  return `@${profile.real_name || profile?.real_name}`;
+  return `@${profile?.real_name || profile?.display_name || "user"}`;

684-705: Optionally try conversations.join before inviting the bot.

For public channels, joining is simpler and avoids invite failures; fall back to invite on error.

-        for (const channel of channelIds) {
+        for (const channel of channelIds) {
           try {
-            // Note: Trying to add the app to DM or group DM channels results in
-            // the error: method_not_supported_for_channel_type
-            await this.inviteToConversation({
-              channel,
-              users: user_id,
-            });
+            // Attempt join (public channels). If it fails, fall back to invite.
+            await this.sdk({ asBot: true }).conversations.join({ channel });
+          } catch (errJoin) {
+            try {
+              await this.inviteToConversation({ channel, users: user_id });
+            } catch (error) {
+              console.log(`Unable to add app to channel ${channel}: ${error}`);
+            }
           } catch (error) {
             console.log(`Unable to add app to channel ${channel}: ${error}`);
           }
         }
components/slack_v2/actions/common/send-message.mjs (2)

205-206: Validate blocks JSON and surface a clear error.

-    } else if (typeof blocks === "string") {
-      blocks = JSON.parse(blocks);
+    } else if (typeof blocks === "string") {
+      try {
+        blocks = JSON.parse(blocks);
+      } catch (e) {
+        throw new Error(`Invalid JSON in "Blocks": ${e.message}`);
+      }
     }

250-253: Validate post_at (ISO-8601, not in the past) before scheduling.

   if (this.post_at) {
-    obj.post_at = Math.floor(new Date(this.post_at).getTime() / 1000);
+    const ts = Date.parse(this.post_at);
+    if (Number.isNaN(ts)) {
+      throw new Error('Invalid "Schedule message" datetime. Use ISO 8601 (e.g., 2014-01-01T00:00:00Z).');
+    }
+    if (ts < Date.now()) {
+      throw new Error('"Schedule message" time cannot be in the past.');
+    }
+    obj.post_at = Math.floor(ts / 1000);
     return await this.slack.scheduleMessage(obj);
   }
components/slack_v2/actions/common/build-blocks.mjs (3)

160-164: Defensive: ensure this.blocks is an array in “array” mode.

Prevents runtime issues if upstream passes a non‑array.

-    if (this.passArrayOrConfigure === "array") {
-      blocks = this.blocks;
+    if (this.passArrayOrConfigure === "array") {
+      blocks = Array.isArray(this.blocks) ? this.blocks : [];
     } else {

73-75: Avoid deprecated substr; prefer slice (or crypto.randomUUID() for stronger IDs).

Small DX fix; optional stronger ID generation shown below.

-          action_id: `actionId-${Math.random().toString(36)
-            .substr(2, 9)}`,  // Generates a random action_id
+          action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`,  // Generates a random action_id

Optional stronger IDs:

// add at top of file:
import { randomUUID } from "crypto";
// and here:
action_id: `actionId-${randomUUID()}`,

39-82: Handle unknown block types explicitly.

Fail fast if an unsupported type is passed.

       } else if (type === "link_button") {
@@
         return {
           type: "actions",
           elements: buttons,
         };
       }
+      throw new Error(`Unsupported block type: ${type}`);
components/slack_v2/sources/common/base.mjs (2)

1-1: Use stable ID generation; avoid deprecated substr.

Prefer crypto.randomUUID() (Node 16+) for uniqueness and clarity; fallback to slice if needed.

+import { randomUUID } from "crypto";
@@
-        event.pipedream_msg_id = `pd_${Date.now()}_${Math.random()
-          .toString(36)
-          .substr(2, 10)}`;
+        event.pipedream_msg_id = `pd_${Date.now()}_${randomUUID()}`;

If crypto isn’t available, minimally:

-          .substr(2, 10)}`;
+          .slice(2, 12)}`;

Also applies to: 172-175


177-181: Harden $emit id fallback chain.

If event.channel is a string, .id is undefined. Use safe fallbacks.

-      this.$emit(event, {
-        id: event.client_msg_id || event.pipedream_msg_id || event.channel.id,
+      this.$emit(event, {
+        id: event.client_msg_id
+          || event.pipedream_msg_id
+          || (event.channel && (event.channel.id || event.channel))
+          || event.user
+          || String(event.event_ts || Date.now()),
         summary: this.getSummary(event),
         ts: event.event_ts || Date.now(),
       });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2df5de6 and f417859.

📒 Files selected for processing (57)
  • components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1 hunks)
  • components/slack_v2/actions/approve-workflow/approve-workflow.mjs (1 hunks)
  • components/slack_v2/actions/archive-channel/archive-channel.mjs (1 hunks)
  • components/slack_v2/actions/common/build-blocks.mjs (1 hunks)
  • components/slack_v2/actions/common/send-message.mjs (1 hunks)
  • components/slack_v2/actions/create-channel/create-channel.mjs (1 hunks)
  • components/slack_v2/actions/create-reminder/create-reminder.mjs (1 hunks)
  • components/slack_v2/actions/delete-file/delete-file.mjs (1 hunks)
  • components/slack_v2/actions/delete-message/delete-message.mjs (1 hunks)
  • components/slack_v2/actions/find-message/find-message.mjs (1 hunks)
  • components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs (1 hunks)
  • components/slack_v2/actions/get-current-user/get-current-user.mjs (1 hunks)
  • components/slack_v2/actions/get-file/get-file.mjs (1 hunks)
  • components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1 hunks)
  • components/slack_v2/actions/kick-user/kick-user.mjs (1 hunks)
  • components/slack_v2/actions/list-channels/list-channels.mjs (1 hunks)
  • components/slack_v2/actions/list-files/list-files.mjs (1 hunks)
  • components/slack_v2/actions/list-group-members/list-group-members.mjs (1 hunks)
  • components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1 hunks)
  • components/slack_v2/actions/list-replies/list-replies.mjs (1 hunks)
  • components/slack_v2/actions/list-users/list-users.mjs (1 hunks)
  • components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs (1 hunks)
  • components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1 hunks)
  • components/slack_v2/actions/send-large-message/send-large-message.mjs (1 hunks)
  • components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1 hunks)
  • components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs (1 hunks)
  • components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs (1 hunks)
  • components/slack_v2/actions/send-message/send-message.mjs (1 hunks)
  • components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1 hunks)
  • components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1 hunks)
  • components/slack_v2/actions/set-status/set-status.mjs (1 hunks)
  • components/slack_v2/actions/update-group-members/update-group-members.mjs (1 hunks)
  • components/slack_v2/actions/update-message/update-message.mjs (1 hunks)
  • components/slack_v2/actions/update-profile/update-profile.mjs (1 hunks)
  • components/slack_v2/actions/upload-file/upload-file.mjs (1 hunks)
  • components/slack_v2/common/constants.mjs (1 hunks)
  • components/slack_v2/package.json (1 hunks)
  • components/slack_v2/slack_v2.app.mjs (1 hunks)
  • components/slack_v2/sources/common/base.mjs (1 hunks)
  • components/slack_v2/sources/common/constants.mjs (1 hunks)
  • components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1 hunks)
  • components/slack_v2/sources/new-channel-created/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-interaction-event-received/README.md (1 hunks)
  • components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs (1 hunks)
  • components/slack_v2/sources/new-interaction-event-received/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (1 hunks)
  • components/slack_v2/sources/new-keyword-mention/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs (1 hunks)
  • components/slack_v2/sources/new-message-in-channels/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (1 hunks)
  • components/slack_v2/sources/new-reaction-added/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-saved-message/new-saved-message.mjs (1 hunks)
  • components/slack_v2/sources/new-saved-message/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-user-added/new-user-added.mjs (1 hunks)
  • components/slack_v2/sources/new-user-added/test-event.mjs (1 hunks)
  • components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1 hunks)
  • components/slack_v2/sources/new-user-mention/test-event.mjs (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • components/slack_v2/sources/new-keyword-mention/test-event.mjs
  • components/slack_v2/sources/new-user-mention/test-event.mjs
🧰 Additional context used
🧬 Code graph analysis (26)
components/slack_v2/actions/list-channels/list-channels.mjs (4)
components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
  • params (45-49)
  • page (50-50)
  • nextCursor (53-55)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
  • params (43-46)
  • page (47-47)
  • nextCursor (50-52)
components/slack_v2/actions/list-replies/list-replies.mjs (3)
  • params (51-55)
  • page (56-56)
  • nextCursor (59-61)
components/slack_v2/actions/list-users/list-users.mjs (3)
  • params (38-41)
  • page (42-42)
  • nextCursor (45-47)
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (3)
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
  • response (32-34)
components/slack_v2/actions/create-channel/create-channel.mjs (1)
  • response (33-36)
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
  • response (30-33)
components/slack_v2/actions/list-files/list-files.mjs (5)
components/slack_v2/actions/list-channels/list-channels.mjs (1)
  • params (31-33)
components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
  • params (45-49)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1)
  • params (43-46)
components/slack_v2/actions/list-replies/list-replies.mjs (1)
  • params (51-55)
components/slack_v2/actions/list-users/list-users.mjs (1)
  • params (38-41)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
  • response (37-41)
components/slack_v2/actions/list-replies/list-replies.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
  • params (31-33)
  • page (34-34)
  • nextCursor (37-39)
components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
  • params (45-49)
  • page (50-50)
  • nextCursor (53-55)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
  • params (43-46)
  • page (47-47)
  • nextCursor (50-52)
components/slack_v2/actions/list-users/list-users.mjs (3)
  • params (38-41)
  • page (42-42)
  • nextCursor (45-47)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (2)
  • msgType (61-66)
  • emitEvent (86-86)
components/slack_v2/actions/kick-user/kick-user.mjs (2)
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
  • response (32-34)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
  • response (31-34)
components/slack_v2/actions/list-users/list-users.mjs (2)
components/slack_v2/slack_v2.app.mjs (4)
  • users (517-519)
  • users (632-640)
  • users (656-664)
  • page (293-298)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
  • params (31-33)
  • page (34-34)
  • nextCursor (37-39)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
  • response (40-44)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
components/slack_v2/actions/common/send-message.mjs (2)
  • resp (254-254)
  • resp (255-257)
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1)
  • response (30-33)
components/slack_v2/slack_v2.app.mjs (13)
components/slack_v2/actions/common/send-message.mjs (3)
  • channelId (190-190)
  • resp (254-254)
  • resp (255-257)
components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
  • members (44-44)
  • page (50-50)
  • nextCursor (53-55)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
  • usernames (59-59)
  • page (47-47)
  • nextCursor (50-52)
components/slack_v2/actions/list-channels/list-channels.mjs (2)
  • page (34-34)
  • nextCursor (37-39)
components/slack_v2/actions/list-replies/list-replies.mjs (2)
  • page (56-56)
  • nextCursor (59-61)
components/slack_v2/actions/list-users/list-users.mjs (3)
  • page (42-42)
  • users (37-37)
  • nextCursor (45-47)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
  • response (40-44)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
  • response (33-35)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
  • response (37-41)
components/slack_v2/actions/get-file/get-file.mjs (1)
  • response (53-55)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
  • response (31-34)
components/slack_v2/actions/kick-user/kick-user.mjs (1)
  • response (42-45)
components/slack_v2/actions/send-large-message/send-large-message.mjs (2)
  • response (81-81)
  • response (88-90)
components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1)
components/slack_v2/actions/create-channel/create-channel.mjs (1)
  • name (31-31)
components/slack_v2/actions/list-group-members/list-group-members.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
  • params (31-33)
  • page (34-34)
  • nextCursor (37-39)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
  • params (43-46)
  • page (47-47)
  • nextCursor (50-52)
components/slack_v2/actions/list-users/list-users.mjs (4)
  • params (38-41)
  • page (42-42)
  • nextCursor (45-47)
  • users (37-37)
components/slack_v2/slack_v2.app.mjs (4)
  • page (293-298)
  • users (517-519)
  • users (632-640)
  • users (656-664)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
components/slack_v2/actions/kick-user/kick-user.mjs (1)
  • response (42-45)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
components/slack_v2/actions/get-file/get-file.mjs (1)
  • response (53-55)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
  • params (31-33)
  • page (34-34)
  • nextCursor (37-39)
components/slack_v2/actions/list-group-members/list-group-members.mjs (4)
  • params (45-49)
  • page (50-50)
  • nextCursor (53-55)
  • members (44-44)
components/slack_v2/actions/list-replies/list-replies.mjs (3)
  • params (51-55)
  • page (56-56)
  • nextCursor (59-61)
components/slack_v2/actions/list-users/list-users.mjs (3)
  • params (38-41)
  • page (42-42)
  • nextCursor (45-47)
components/slack_v2/actions/common/send-message.mjs (4)
components/slack_v2/actions/common/build-blocks.mjs (2)
  • props (86-86)
  • blocks (161-161)
components/slack_v2/actions/send-large-message/send-large-message.mjs (4)
  • sentViaPipedreamText (45-45)
  • metadataEventPayload (49-49)
  • obj (66-79)
  • channelName (91-91)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
  • resp (43-45)
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)
  • resp (70-72)
components/slack_v2/actions/common/build-blocks.mjs (2)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
  • blocks (75-81)
components/slack_v2/actions/common/send-message.mjs (1)
  • blocks (198-198)
components/slack_v2/sources/common/base.mjs (1)
components/slack_v2/sources/common/constants.mjs (4)
  • NAME_CACHE_TIMEOUT (50-50)
  • NAME_CACHE_TIMEOUT (50-50)
  • NAME_CACHE_MAX_SIZE (49-49)
  • NAME_CACHE_MAX_SIZE (49-49)
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (2)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
  • resp (43-45)
components/slack_v2/actions/common/send-message.mjs (2)
  • resp (254-254)
  • resp (255-257)
components/slack_v2/actions/send-large-message/send-large-message.mjs (1)
components/slack_v2/actions/common/send-message.mjs (4)
  • sentViaPipedreamText (209-209)
  • metadataEventPayload (213-213)
  • obj (231-248)
  • channelName (258-258)
components/slack_v2/actions/find-message/find-message.mjs (1)
components/slack_v2/slack_v2.app.mjs (4)
  • cursor (187-196)
  • cursor (240-247)
  • cursor (629-629)
  • cursor (653-653)
components/slack_v2/actions/update-message/update-message.mjs (2)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
  • response (40-44)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
  • response (37-41)
components/slack_v2/actions/get-file/get-file.mjs (1)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
  • response (33-35)
components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (1)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (2)
  • msgType (68-74)
  • emitEvent (101-101)
🪛 Gitleaks (8.28.0)
components/slack_v2/sources/new-interaction-event-received/test-event.mjs

[high] 11-11: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 LanguageTool
components/slack_v2/sources/new-interaction-event-received/README.md

[style] ~11-~11: Consider using just “reply”.
Context: ...k with other APIs or services, and then reply back to the original message. # Getting Sta...

(RETURN_BACK)


[style] ~85-~85: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hat this source is configured with. 1. Make sure that the message was sent via Pipe...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Lint Code Base
  • GitHub Check: pnpm publish
  • GitHub Check: Verify TypeScript components
🔇 Additional comments (24)
components/slack_v2/sources/new-user-added/test-event.mjs (1)

1-48: Fixture looks sanitized and complete.

No functional issues found. Safe defaults (empty strings) avoid PII exposure.

components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (1)

15-23: Confirm conversation prop supports multiple selection.

You set type: "string[]" but reuse a single conversation propDefinition. Ensure the underlying definition returns an array and UI supports multi-select; otherwise switch to a multi-select propDefinition or wrap with options() to enable multiple values.

components/slack_v2/actions/create-channel/create-channel.mjs (1)

29-39: LGTM! Clean channel name normalization and creation flow.

The channel name normalization (replacing whitespace with hyphens and lowercasing) correctly aligns with Slack's channel naming requirements. The API call structure and success summary are appropriate.

components/slack_v2/actions/approve-workflow/approve-workflow.mjs (1)

47-91: LGTM! Well-structured workflow approval with proper flow suspension.

The implementation correctly:

  • Suspends the flow to obtain resume/cancel URLs
  • Posts a formatted Slack message with interactive buttons
  • Links the buttons to the appropriate flow control endpoints
  • Handles the special "Channels" value to map to both PUBLIC and PRIVATE channel types
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1)

29-36: LGTM!

The implementation correctly maps the UI concept of "channel description" to Slack's API terminology of "purpose". The parameter mapping and API call are consistent with the Slack documentation.

components/slack_v2/sources/new-channel-created/test-event.mjs (1)

1-44: LGTM!

The test fixture provides a comprehensive static event payload that matches the Slack channel_created event structure, including all relevant channel properties and metadata.

components/slack_v2/sources/new-saved-message/test-event.mjs (1)

1-37: LGTM!

The test fixture correctly represents a Slack star_added event with appropriate nested structure for the item and message properties, including the blocks array for rich text formatting.

components/slack_v2/actions/list-replies/list-replies.mjs (1)

58-65: The original code is correct. No changes needed.

The Slack API conversations.replies endpoint returns response_metadata containing next_cursor as a standard part of the response structure for cursor-based pagination. The destructuring on line 60 is safe because the Slack API guarantees this field exists.

The pattern used in list-replies is consistent across all list actions in the codebase (list-channels, list-group-members, list-members-in-channel, list-users), and none include defensive handling. This systematic consistency reflects the proper reliance on the Slack API contract.

Likely an incorrect or invalid review comment.

components/slack_v2/actions/list-group-members/list-group-members.mjs (1)

52-59: Verify response_metadata structure for usergroups.users.list API call.

The web search results are conflicting on whether usergroups.users.list includes response_metadata in its response. One source indicates the response contains only {"ok": true, "users": [...]} without response_metadata, which would cause a destructuring error on line 54. However, pagination documentation references response_metadata.next_cursor for this endpoint.

The pattern is consistent across all list actions in the codebase, suggesting it may work in practice, but the API documentation ambiguity warrants verification—especially since find-message.mjs uses defensive optional chaining for response_metadata, indicating some endpoints handle this differently.

Recommended actions:

  • Test this action against the actual Slack API to confirm the response structure
  • If response_metadata is missing, add defensive handling: response_metadata: { next_cursor: nextCursor } = {}
  • Check Slack API changelog or test against a real workspace to resolve documentation ambiguity
components/slack_v2/actions/get-current-user/get-current-user.mjs (1)

25-31: ****

The usersInfo method does exist in the v2 wrapper (defined at line 865 of slack_v2.app.mjs). The review comment's assumption that it may not exist is unfounded. The codebase shows that while some methods follow a getXxx pattern (e.g., getUserProfile, getTeamInfo), others like usersInfo and authTest use different naming conventions—all valid in the v2 wrapper.

Likely an incorrect or invalid review comment.

components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs (1)

16-44: Review comment is incorrect — props merge order concern is unfounded

The common.props object does not contain conversation, text, or mrkdwn properties. It only exports: slack, as_user, addToChannel, post_at, include_sent_via_pipedream_flag, customizeBotSettings, username, icon_emoji, icon_url, replyToThread, thread_ts, thread_broadcast, addMessageMetadata, metadata_event_type, metadata_event_payload, configureUnfurlSettings, unfurl_links, unfurl_media.

Since these keys don't exist in common.props, spreading ...common.props at the end cannot override the custom conversation, text, or mrkdwn props with their PUBLIC/PRIVATE channel restrictions. The current code structure is correct and doesn't require refactoring.

Likely an incorrect or invalid review comment.

components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs (1)

16-53: Prop name is correct; no changes needed.

The thread_broadcast prop is correct. In send-message.mjs, it's intentionally mapped to the Slack API field reply_broadcast in the run method (reply_broadcast: this.thread_broadcast). The review comment's concern about prop naming is factually incorrect.

Regarding props override order: While moving ...common.props to the beginning would be more conventional, the current structure works correctly because explicit props after the spread override the spread defaults via JavaScript object semantics. This is a style suggestion only, not a critical issue.

Likely an incorrect or invalid review comment.

components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)

1-37: LGTM!

The action follows the standard Slack v2 pattern with proper prop definitions and a clean implementation. The API call and summary export are correctly structured.

components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1)

1-32: LGTM!

The event source follows the standard Slack v2 pattern with proper extension of the common base, correct event name configuration, and a clear summary method.

components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs (1)

1-85: LGTM!

The action properly handles both conversation selection and user list scenarios with appropriate validation. The ConfigurationError for missing inputs and the openConversation flow are well-implemented.

components/slack_v2/actions/list-channels/list-channels.mjs (1)

29-51: LGTM!

The pagination logic correctly accumulates channels using cursor-based pagination and respects the page limit. The implementation is consistent with other list actions in the codebase.

components/slack_v2/actions/send-message/send-message.mjs (1)

1-56: LGTM!

The action properly implements dynamic channel type filtering based on user selection. The conditional logic for constraining conversation types is clean and correct.

components/slack_v2/actions/find-message/find-message.mjs (1)

57-60: Verify sort_dir parameter support and consider exposing channel types

Based on Slack's documentation, the assistant.search.context API supports channel_types as a comma-separated list of public_channel, private_channel, mpim, and im. However, your code hardcodes only "public_channel,private_channel", which excludes direct messages and multi-party IMs that the API supports.

More critically, sort_dir is documented for Slack's classic search APIs but not explicitly documented for assistant.search.context. Your code passes sort_dir: this.sortDirection to the endpoint—verify that this parameter is actually supported by the assistant.search.context endpoint, as it may be silently ignored or cause unexpected behavior.

Recommendations:

  • Expose channelTypes as a configurable prop (defaulting to the current value to preserve behavior).
  • Test or confirm with Slack that sort_dir is supported by assistant.search.context before relying on it in production.
components/slack_v2/actions/get-file/get-file.mjs (1)

46-58: LGTM — action is minimal and correct.

Good use of maybeAddAppToChannels and clear summary. No blocking issues.

components/slack_v2/actions/delete-message/delete-message.mjs (1)

28-34: Fix description text only; as_user parameter is valid and supported by Slack API.

The as_user parameter is supported by Slack's chat.delete method (as of October 20, 2025). It's an optional boolean that, when true, deletes the message as the authed user. The only issue is the description text incorrectly says "update" instead of "delete".

Apply this fix:

    as_user: {
      propDefinition: [
        slack,
        "as_user",
      ],
-      description: "Pass true to update the message as the authed user. Bot users in this context are considered authed users.",
+      description: "Pass true to delete the message as the authed user. Bot users in this context are considered authed users.",
    },

Also applies to: 37-41

components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)

33-41: No actionable issues found. The code is correct.

The conversations prop contains an array of channel IDs (confirmed by the prop definition type "string[]" and description "Select one or more channels to monitor for new messages"). The eventNames() method correctly returns either:

  • The selected channel IDs when channels are chosen, or
  • ["message"] as the fallback event type when no channels are selected

Both are valid values per the Slack apphook contract. This pattern is consistently applied across related sources (new-message-in-channels, new-keyword-mention).

components/slack_v2/actions/archive-channel/archive-channel.mjs (1)

21-27: Review comment is incorrect — ignore this suggestion.

Slack's conversations.archive API does support archiving IM and MPIM conversations when those conversation types are supported and the token has corresponding scopes (im:write and mpim:write). The current code allowing MPIM is correct and will not error at runtime with appropriate token permissions. Do not apply the suggested diff.

Likely an incorrect or invalid review comment.

components/slack_v2/sources/common/base.mjs (1)

1-4: All required Slack app client methods are present.

Verification confirms that usersInfo, conversationsInfo, getConversationReplies, getBotInfo, and getTeamInfo all exist in the slack_v2.app.mjs wrapper. No integration breakage risk from missing methods.

components/slack_v2/actions/common/build-blocks.mjs (1)

99-102: Path verification successful; brittleness concern remains valid.

The path common.props.slack.propDefinitions.blocks exists and exposes the three accessed properties:

  • type"string"
  • label"Blocks"
  • description → populated string

However, the underlying concern about brittleness stands: deep nested property access is inherently fragile. If the structure of slack_v2.app.mjs or send-message.mjs changes during refactoring, these references could break without warning. Consider extracting this to a named constant or utility function to isolate the dependency path.

@@ -0,0 +1,48 @@
import slack from "../../slack.app.mjs";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix incorrect import path.

This file imports from "../../slack.app.mjs" while all other Slack v2 actions import from "../../slack_v2.app.mjs". This inconsistency will likely cause a module resolution error since this is a Slack v2 component.

Apply this diff:

-import slack from "../../slack.app.mjs";
+import slack from "../../slack_v2.app.mjs";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import slack from "../../slack.app.mjs";
import slack from "../../slack_v2.app.mjs";
🤖 Prompt for AI Agents
In components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs around
line 1, the import path uses "../../slack.app.mjs" which is inconsistent with
other Slack v2 actions; update the import to point to "../../slack_v2.app.mjs"
so the module resolves correctly and matches the v2 component convention.

Comment on lines +65 to +76
const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
text: {
type: "plain_text",
text: buttonText,
emoji: true,
},
url: text[buttonText], // Access the URL using buttonText as the key
action_id: `actionId-${Math.random().toString(36)
.substr(2, 9)}`, // Generates a random action_id
}));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate link_button input shape and guard keys.

Object.keys(text) will throw if text isn’t a plain object; also clarify error early.

-      } else if (type === "link_button") {
-        const buttons = Object.keys(text).map((buttonText) => ({
+      } else if (type === "link_button") {
+        if (!text || typeof text !== "object" || Array.isArray(text)) {
+          throw new Error("Link Button expects an object mapping button text → URL");
+        }
+        const buttons = Object.keys(text).map((buttonText) => ({
           type: "button",
@@
-          action_id: `actionId-${Math.random().toString(36)
-            .substr(2, 9)}`,  // Generates a random action_id
+          action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`,  // Generates a random action_id
         }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
text: {
type: "plain_text",
text: buttonText,
emoji: true,
},
url: text[buttonText], // Access the URL using buttonText as the key
action_id: `actionId-${Math.random().toString(36)
.substr(2, 9)}`, // Generates a random action_id
}));
if (!text || typeof text !== "object" || Array.isArray(text)) {
throw new Error("Link Button expects an object mapping button text → URL");
}
const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
text: {
type: "plain_text",
text: buttonText,
emoji: true,
},
url: text[buttonText], // Access the URL using buttonText as the key
action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id
}));
🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 65 to 76,
the code uses Object.keys(text) without validating that text is a non-null plain
object which will throw for non-objects; update to first guard the input (if
typeof text !== "object" || text === null || Array.isArray(text) then throw a
clear TypeError or return an empty array), then iterate using
Object.entries(text) and filter to only own string key/value pairs (e.g. filter
([k,v]) => typeof k === "string" && typeof v === "string") before mapping to
buttons, and keep generating action_id safely (use slice instead of substr if
desired).

Comment on lines +97 to +103
if (this.passArrayOrConfigure == "array") {
props.blocks = {
type: common.props.slack.propDefinitions.blocks.type,
label: common.props.slack.propDefinitions.blocks.label,
description: common.props.slack.propDefinitions.blocks.description,
};
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use strict equality for mode checks.

Avoid coercion. Switch to === in both places.

-    if (this.passArrayOrConfigure == "array") {
+    if (this.passArrayOrConfigure === "array") {
       props.blocks = {
@@
-    if (this.passArrayOrConfigure == "array") {
-      blocks = this.blocks;
+    if (this.passArrayOrConfigure === "array") {
+      blocks = this.blocks;
     } else {

Also applies to: 160-164

🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 97-103 and
also at 160-164, the code uses loose equality (==) for mode checks which can
cause unintended type coercion; change those comparisons to strict equality
(===) in both locations so the checks compare both value and type (e.g., replace
`== "array"` with `=== "array"` and the corresponding else-if/other check
similarly), run tests/lint to ensure no further occurrences remain.

Comment on lines +42 to +51
async run({ $ }) {
const response = await this.slack.addReminders({
text: this.text,
team_id: this.team_id,
time: this.timestamp,
user: this.user,
});
$.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`);
return response;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add defensive null check before accessing nested property.

The summary export directly accesses response.reminder.id without verifying that response or response.reminder exists. If the Slack API returns an unexpected structure or fails gracefully, this will throw a TypeError.

Consider adding a check or optional chaining:

-    $.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`);
+    $.export("$summary", `Successfully created reminder${response?.reminder?.id ? ` with ID ${response.reminder.id}` : ""}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async run({ $ }) {
const response = await this.slack.addReminders({
text: this.text,
team_id: this.team_id,
time: this.timestamp,
user: this.user,
});
$.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`);
return response;
},
async run({ $ }) {
const response = await this.slack.addReminders({
text: this.text,
team_id: this.team_id,
time: this.timestamp,
user: this.user,
});
$.export("$summary", `Successfully created reminder${response?.reminder?.id ? ` with ID ${response.reminder.id}` : ""}`);
return response;
},
🤖 Prompt for AI Agents
In components/slack_v2/actions/create-reminder/create-reminder.mjs around lines
42 to 51, the code directly accesses response.reminder.id which can throw if
response or response.reminder is null/undefined; add a defensive null check or
use optional chaining to safely read the id (e.g., const id =
response?.reminder?.id) and export a safe summary using a fallback message when
id is missing, ensuring the function still returns the original response.

Comment on lines +33 to +38
const response = await this.slack.deleteFiles({
file: this.file,
});
$.export("$summary", `Successfully deleted file with ID ${this.file}`);
return response;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Add response.ok guard and confirm API method name

Check the Slack API response and fail fast on errors. Also confirm deleteFiles matches the v2 wrapper.

Apply:

   const response = await this.slack.deleteFiles({
     file: this.file,
   });
-  $.export("$summary", `Successfully deleted file with ID ${this.file}`);
-  return response;
+  if (!response?.ok) {
+    throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
+  }
+  $.export("$summary", `Successfully deleted file with ID ${this.file}`);
+  return response;

Verify method presence:


🏁 Script executed:

#!/bin/bash
fd -a 'slack_v2.app.mjs' components | xargs -I{} rg -n 'deleteFiles|files\.delete' {}

Length of output: 70


🏁 Script executed:

#!/bin/bash
# Check how other methods/actions in the codebase handle response.ok or error handling
rg -n 'response\.ok|throw new Error' components/slack_v2/actions --type=mjs -C 2 | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Look at the complete delete-file.mjs file to understand current structure
cat components/slack_v2/actions/delete-file/delete-file.mjs

Length of output: 837


🏁 Script executed:

#!/bin/bash
# Check the slack_v2 wrapper to see how the deleteFiles method implementation handles responses
rg -A 10 'deleteFiles\(args' components/slack_v2/slack_v2.app.mjs

Length of output: 247


🏁 Script executed:

#!/bin/bash
# Find makeRequest implementation
rg -A 15 'makeRequest\(' components/slack_v2/slack_v2.app.mjs | head -50

Length of output: 1497


🏁 Script executed:

#!/bin/bash
# Check how other destructive actions (create, delete, update) handle responses
find components/slack_v2/actions -name "*.mjs" | xargs grep -l "response\.ok\|throw new Error" | head -10

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Look at a similar destructive action to see pattern
find components/slack_v2/actions -name "*delete*" -o -name "*remove*" | head -5 | xargs cat

Length of output: 2021


🏁 Script executed:

#!/bin/bash
# Check response.ok pattern in upload-file action (similar destructive operation)
cat components/slack_v2/actions/upload-file/upload-file.mjs

Length of output: 2575


🏁 Script executed:

#!/bin/bash
# Check response.ok pattern in send-message action
cat components/slack_v2/actions/common/send-message.mjs

Length of output: 6738


Add response.ok guard and verify API method exists

The deleteFiles method correctly maps to Slack's files.delete API and is properly defined in the v2 wrapper. However, the action should verify the API response before exporting the summary. This aligns with error-handling patterns used in similar destructive operations like upload-file.mjs.

Apply:

   const response = await this.slack.deleteFiles({
     file: this.file,
   });
-  $.export("$summary", `Successfully deleted file with ID ${this.file}`);
-  return response;
+  if (!response?.ok) {
+    throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
+  }
+  $.export("$summary", `Successfully deleted file with ID ${this.file}`);
+  return response;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await this.slack.deleteFiles({
file: this.file,
});
$.export("$summary", `Successfully deleted file with ID ${this.file}`);
return response;
},
const response = await this.slack.deleteFiles({
file: this.file,
});
if (!response?.ok) {
throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
}
$.export("$summary", `Successfully deleted file with ID ${this.file}`);
return response;
},
🤖 Prompt for AI Agents
In components/slack_v2/actions/delete-file/delete-file.mjs around lines 33 to
38, the action currently calls this.slack.deleteFiles and unconditionally
exports a success summary; add a guard to (1) verify the API method exists
(throw a clear error if typeof this.slack.deleteFiles !== 'function') before
calling, (2) after awaiting the call check response.ok and if false throw an
Error including response.error or the full response for debugging, and (3) only
export the success summary and return the response when response.ok is true.

Comment on lines +92 to +98
const itemUserResponse = await this.slack.usersInfo({
user: event.user,
});

event.userInfo = userResponse.user;
event.itemUserInfo = itemUserResponse.user;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Fetches the same user twice; should use event.item_user for message author.

This populates itemUserInfo with the reactor instead of the message author.

-        const itemUserResponse = await this.slack.usersInfo({
-          user: event.user,
-        });
+        const itemUserResponse = await this.slack.usersInfo({
+          user: event.item_user,
+        });
@@
-        event.itemUserInfo = itemUserResponse.user;
+        event.itemUserInfo = itemUserResponse.user;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const itemUserResponse = await this.slack.usersInfo({
user: event.user,
});
event.userInfo = userResponse.user;
event.itemUserInfo = itemUserResponse.user;
}
const itemUserResponse = await this.slack.usersInfo({
user: event.item_user,
});
event.userInfo = userResponse.user;
event.itemUserInfo = itemUserResponse.user;
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs around
lines 92 to 98, the code calls usersInfo twice for the same user (reactor) and
assigns the wrong user to event.itemUserInfo; change the second fetch to use
event.item_user (the message author) instead of event.user, and assign the
response to event.itemUserInfo (keep event.userInfo as the reactor from the
first fetch); ensure you call await this.slack.usersInfo({ user: event.item_user
}) and set event.itemUserInfo = <that response>.user.

Comment on lines +36 to +37
"email": "[email protected]",
"first_name": "Test",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use example domains for sample emails.

Replace real-looking emails with reserved example domains to avoid accidental delivery or PII.

-      "email": "[email protected]",
+      "email": "[email protected]",
@@
-      "email": "[email protected]",
+      "email": "[email protected]",

Also applies to: 78-79

🤖 Prompt for AI Agents
In components/slack_v2/sources/new-reaction-added/test-event.mjs around lines
36-37 (and also lines 78-79), the sample email values use a real-looking
address; replace them with reserved example domains like [email protected] or
[email protected] (or [email protected]) to avoid accidental delivery or PII
exposure, updating both occurrences accordingly.

Comment on lines +6 to +11
key: "slack_v2-new-saved-message",
name: "New Saved Message (Instant)",
version: "0.0.7",
description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.",
type: "source",
dedupe: "unique",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Name/event mismatch (“Saved” vs star_added)

Source is named “Saved Message” but listens to star_added (stars). To avoid user confusion, either rename to “New Star Added (Instant)” or update implementation/description to reflect “stars” terminology clearly. Keep the deprecation warning.

🤖 Prompt for AI Agents
In components/slack_v2/sources/new-saved-message/new-saved-message.mjs around
lines 6 to 11, the source is named and described as “Saved Message” but the
event listened for is the Slack star_added event; update naming/description to
match stars or change the event to reflect saved/bookmarked messages.
Specifically, either rename key/name to something like "slack_v2-new-star-added"
and the display name to "New Star Added (Instant)" and update description text
to reference "star" events (keeping the existing deprecation warning), or if the
intent is to track saved/bookmarked messages, change the implementation to
subscribe to the correct Slack event for saved messages and update the
description accordingly.

Comment on lines +15 to +23
slackApphook: {
type: "$.interface.apphook",
appProp: "slack",
async eventNames() {
return [
"star_added",
];
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Confirm star_added viability and document shutdown fallback

Slack has been deprecating “stars”. Please confirm star_added is still emitted for workspaces and note expected EOL. If deprecated for most tenants, consider adding a “Deprecated” badge/annotation in the component metadata and linking to a migration alternative, if any.


🌐 Web query:

Is Slack’s Events API `star_added` event deprecated or removed? What’s the current recommendation for “saved messages”?

💡 Result:

Short answer: The star (saved) events have been effectively retired. Slack’s stars APIs/events (including star_added) are no longer emitted for end‑user “saved” activity and Slack recommends retiring app functionality that depends on the stars APIs. For end users Slack now uses the “Later” / Saved view for saved messages, but there are no public “Later” APIs to replace star events. [1][2]

Sources:

  • stars.add method docs (notes that stars can no longer be viewed/interacted with by end users, the star_added event is no longer emitted, and apps should retire stars APIs). [1]
  • Slack UI docs describing the Later / Saved for later view (current user-facing replacement for saved messages). [2]

Remove or deprecate the star_added event—it's no longer emitted by Slack

Slack's stars APIs/events (including star_added) are no longer emitted for end‑user "saved" activity, and Slack recommends retiring app functionality that depends on the stars APIs. The component as written will not receive events. There are no public "Later" APIs to replace star events, so this integration cannot be directly migrated. Consider either removing this event source or marking the component as deprecated with a link to Slack's migration guidance.

🤖 Prompt for AI Agents
In components/slack_v2/sources/new-saved-message/new-saved-message.mjs around
lines 15 to 23, the component is subscribing to the deprecated Slack event
"star_added" which is no longer emitted; either remove "star_added" from the
eventNames array so the source no longer registers for a non‑existent event, or
mark the component as deprecated (add a deprecation flag/comment and update
README/metadata with a short deprecation notice and Slack migration guidance
link) and update any runtime warnings/logs to surface the deprecation to
integrators.

Comment on lines +75 to +82
const [
{
elements: [
{ elements = [] } = {},
] = [],
} = {},
] = blocks;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Emit logic requires user AND keyword; should be OR. Also fragile block parsing and possible TypeError on undefined text.

Current code:

  • Scans only the first block/section; misses mentions in later blocks.
  • Requires userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1) which contradicts “username or specific keyword” and can throw if text is undefined.

Fix: parse all blocks, add <@USER> text fallback, make keyword match safe and case-insensitive, and emit on (userMatch OR keywordMatch) OR PD history.

Apply:

@@
-      const [
-        {
-          elements: [
-            { elements = [] } = {},
-          ] = [],
-        } = {},
-      ] = blocks;
+      // Collect inner elements from all blocks / sections
+      const blockElements = [];
+      for (const b of (blocks || [])) {
+        for (const el of (b.elements || [])) {
+          if (el && Array.isArray(el.elements)) {
+            blockElements.push(...el.elements);
+          }
+        }
+      }
@@
-      let emitEvent = false;
-      if (elements) {
-        let userMatch = false;
-        for (const item of elements) {
-          if (item.user_id && item.user_id === this.user) {
-            userMatch = true;
-            break;
-          }
-        }
-        if (userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1)) {
-          emitEvent = true;
-        }
-      }
-      if (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE) {
-        emitEvent = true;
-      }
+      const t = typeof text === "string" ? text : "";
+      // Detect user mention via blocks and fallback to text markup
+      let userMatch = blockElements.some((item) => item && item.user_id === this.user);
+      if (!userMatch && this.user && t.includes(`<@${this.user}>`)) {
+        userMatch = true;
+      }
+      // Keyword match (case-insensitive), guarded against undefined text
+      const kw = this.keyword?.trim();
+      const keywordMatch = kw ? t.toLowerCase().includes(kw.toLowerCase()) : false;
+      // Emit if user OR keyword matches, or always for PD history
+      const emitEvent = userMatch || keywordMatch || (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE);
@@
-      if (emitEvent) {
+      if (emitEvent) {
         return event;
       }

Also applies to: 101-116

🤖 Prompt for AI Agents
In components/slack_v2/sources/new-user-mention/new-user-mention.mjs around
lines 75-82 (also apply same changes to 101-116), the current destructuring only
inspects the first block, can throw if text is undefined, and uses an AND when
it should be an OR for user vs keyword matches; change to iterate over all
blocks and their elements, safely extract text (fallback to the raw "<@USER>"
mention string if element text is missing), normalize text to lowercase before
matching to make keyword checks case-insensitive, compute userMatch =
Boolean(text.match(/<@(\w+)>/)) and keywordMatch = this.keyword &&
text.toLowerCase().includes(this.keyword.toLowerCase()), then emit when
userMatch OR keywordMatch is true (also include PD history emit as before), and
guard all accesses to avoid TypeErrors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant